/**
 * Software License, Version 1.0
 * 
 * Copyright 2003 The Trustees of Indiana University.  All rights reserved.
 * 
 *
 *Redistribution and use in source and binary forms, with or without 
 *modification, are permitted provided that the following conditions are met:
 *
 *1) All redistributions of source code must retain the above copyright notice,
 * the list of authors in the original source code, this list of conditions and
 * the disclaimer listed in this license;
 *2) All redistributions in binary form must reproduce the above copyright 
 * notice, this list of conditions and the disclaimer listed in this license in
 * the documentation and/or other materials provided with the distribution;
 *3) Any documentation included with all redistributions must include the 
 * following acknowledgement:
 *
 *"This product includes software developed by the Community Grids Lab. For 
 * further information contact the Community Grids Lab at 
 * http://communitygrids.iu.edu/."
 *
 * Alternatively, this acknowledgement may appear in the software itself, and 
 * wherever such third-party acknowledgments normally appear.
 * 
 *4) The name Indiana University or Community Grids Lab or NaradaBrokering, 
 * shall not be used to endorse or promote products derived from this software 
 * without prior written permission from Indiana University.  For written 
 * permission, please contact the Advanced Research and Technology Institute 
 * ("ARTI") at 351 West 10th Street, Indianapolis, Indiana 46202.
 *5) Products derived from this software may not be called NaradaBrokering, 
 * nor may Indiana University or Community Grids Lab or NaradaBrokering appear
 * in their name, without prior written permission of ARTI.
 * 
 *
 * Indiana University provides no reassurances that the source code provided 
 * does not infringe the patent or any other intellectual property rights of 
 * any other entity.  Indiana University disclaims any liability to any 
 * recipient for claims brought by any other entity based on infringement of 
 * intellectual property rights or otherwise.  
 *
 *LICENSEE UNDERSTANDS THAT SOFTWARE IS PROVIDED "AS IS" FOR WHICH NO 
 *WARRANTIES AS TO CAPABILITIES OR ACCURACY ARE MADE. INDIANA UNIVERSITY GIVES
 *NO WARRANTIES AND MAKES NO REPRESENTATION THAT SOFTWARE IS FREE OF 
 *INFRINGEMENT OF THIRD PARTY PATENT, COPYRIGHT, OR OTHER PROPRIETARY RIGHTS. 
 *INDIANA UNIVERSITY MAKES NO WARRANTIES THAT SOFTWARE IS FREE FROM "BUGS", 
 *"VIRUSES", "TROJAN HORSES", "TRAP DOORS", "WORMS", OR OTHER HARMFUL CODE.  
 *LICENSEE ASSUMES THE ENTIRE RISK AS TO THE PERFORMANCE OF SOFTWARE AND/OR 
 *ASSOCIATED MATERIALS, AND TO THE PERFORMANCE AND VALIDITY OF INFORMATION 
 *GENERATED USING SOFTWARE.
 */
package cgl.narada.transport.ptcp.psocket;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.Socket;
import java.net.SocketException;
import java.net.UnknownHostException;

import cgl.narada.transport.ptcp.psocket.tools.PTCPParameters;

/**
 * <p>Title: </p>
 * <p>Description: </p>
 * <p>Copyright: Copyright (c) 2004</p>
 * <p>Company: </p>
 * @author not attributable
 * @version 1.0
 */
public class PTCPSocket extends Socket implements PTCPParameters {
  private boolean increase_performance_ = false;
  private long tx_end_time_ = 0;
  private long tx_start_time_ = 0;
  private Socket comm_socket_ = null; //Communication socket
  private String id_ = null; //ID number of this socket
  private Socket[] data_sockets_ = null; //Data stream sockets
  private int BANDWIDTH = 10 * 1000 * 1000; //Bandwidth in MBytes/sec
  private int TCPWINDOWSIZE = 8192;
  private int number_of_streams_ = 0; //number of streams
  private long RTT = 0;
  private long end_RTT_ = 0;
  private long start_RTT_ = 0;
  private String module_name_ = "PTCPSocket: ";

  public PTCPSocket(int num) {
    number_of_streams_ = num;
    data_sockets_ = new Socket[num];
  }

  public PTCPSocket(int num, boolean incPrf) {
    number_of_streams_ = num;
    data_sockets_ = new Socket[num];
    increase_performance_ = incPrf;
  }

  /**
   * Constructor.
   * Creates a stream socket and connects it to the specified port
   * number on the named host.
   *
   * @param host remote host name.
   * @param port remote server port.
   * @param num  number of parallel streams.
   *
   */
  public PTCPSocket(String host, int port, int num) {
    try {
      number_of_streams_ = num;
      comm_socket_ = new Socket(host, port);
      id_ = createId();
      init();
    }
    catch (Exception e) {
      e.printStackTrace();
    }
  }

  public PTCPSocket(String host, int port, int num, boolean incPrf) {
    try {
      number_of_streams_ = num;
      comm_socket_ = new Socket(host, port);
      id_ = createId();
      increase_performance_ = incPrf;
      init();
    }
    catch (Exception e) {
      e.printStackTrace();
    }
  }

  /**
   * Constructor.
   * Creates a stream socket and connects it to the specified port
   * number on the specified address.
   *
   * @param address remote internet address.
   * @param port    remote server port.
   * @param num     number of parallel streams.
   */
  public PTCPSocket(InetAddress address, int port, int num) {
    try {
      number_of_streams_ = num;
      comm_socket_ = new Socket(address, port);
      id_ = createId();
      init();
    }
    catch (Exception e) {
      e.printStackTrace();
    }
  }

  public PTCPSocket(InetAddress address, int port, int num, boolean incPrf) {
    try {
      number_of_streams_ = num;
      comm_socket_ = new Socket(address, port);
      id_ = createId();
      increase_performance_ = incPrf;
      init();
    }
    catch (Exception e) {
      e.printStackTrace();
    }
  }

  /**
   * Creates a socket and connects it to the specified remote host on
   * the specified remote port. The Socket
   * will also bind() to the local address and port supplied.
   */
  public PTCPSocket(String host, int port, InetAddress localAddr, int localPort,
                 int num) {
    try {
      number_of_streams_ = num;
      comm_socket_ = new Socket(host, port, localAddr, localPort);
      id_ = createId();
      init();
    }
    catch (Exception e) {
      e.printStackTrace();
    }
  }

  public PTCPSocket(String host, int port, InetAddress localAddr, int localPort,
                 int num, boolean incPrf) {
    try {
      number_of_streams_ = num;
      comm_socket_ = new Socket(host, port, localAddr, localPort);
      id_ = createId();
      increase_performance_ = incPrf;
      init();
    }
    catch (Exception e) {
      e.printStackTrace();
    }
  }

  /**
   * creates a socket and connects it to the specified remote address
   * on the specified remote port. The
   * Socket will also bind() to the local address and port supplied.
   */
  public PTCPSocket(InetAddress address, int port, InetAddress localAddr,
                 int localPort, int num) {
    try {
      number_of_streams_ = num;
      comm_socket_ = new Socket(address, port, localAddr, localPort);
      id_ = createId();
      init();
    }
    catch (Exception e) {
      e.printStackTrace();
    }
  }

  public PTCPSocket(InetAddress address, int port, InetAddress localAddr,
                 int localPort, int num, boolean incPrf) {
    try {
      number_of_streams_ = num;
      comm_socket_ = new Socket(address, port, localAddr, localPort);
      id_ = createId();
      increase_performance_ = incPrf;
      init();
    }
    catch (Exception e) {
      e.printStackTrace();
    }
  }

  /**
   * Get input stream for commands.
   */
  public InputStream getCommInputStream() throws IOException {
    return comm_socket_.getInputStream();
  }

  /**
   * Get output stream for commands.
   */
  public OutputStream getCommOutputStream() throws IOException {
    return comm_socket_.getOutputStream();
  }

  /**
   * Returns the address to which the socket is connected.
   */
  public InetAddress getInetAddress() {
    return comm_socket_.getInetAddress();
  }

  /**
   * Returns an input stream for this socket.
   */
  public InputStream getInputStream() throws IOException {
    InputStream[] tinputs = new InputStream[number_of_streams_];

    try {
      for (int i = 0; i < number_of_streams_; i++) {
        tinputs[i] = data_sockets_[i].getInputStream();
      }
    }
    catch (IOException e) {
      throw e;
    }

    PTCPInputStream input = new PTCPInputStream(tinputs,increase_performance_);

    //txEndTime_ = input.txEndTime_;
    return input;
  }

  /**
   * Enable/disable SO_KEEPALIVE.
   */
  public void setKeepAlive(boolean on) throws SocketException {
    try {
      if (comm_socket_ != null) {
        comm_socket_.setKeepAlive(on);
      }

      if (data_sockets_ != null) {
        for (int i = 0; i < number_of_streams_; i++) {
          data_sockets_[i].setKeepAlive(on);
        }
      }
    }
    catch (SocketException e) {
      throw e;
    }
  }

  /**
   * Tests if SO_KEEPALIVE is enabled.
   */
  public boolean getKeepAlive() throws SocketException {
    try {
      return comm_socket_.getKeepAlive();
    }
    catch (SocketException e) {
      throw e;
    }
  }

  /**
   * Gets the local address to which the socket is bound.
   */
  public InetAddress getLocalAddress() {
    return comm_socket_.getLocalAddress();
  }

  /**
   * Returns the local port to which this socket is bound.
   */
  public int getLocalPort() {
    return comm_socket_.getPort();
  }

  /**
   * Returns an output stream for this socket.
   */
  public OutputStream getOutputStream() throws IOException {
    // set up parallel data streams
    OutputStream[] toutputs = new OutputStream[number_of_streams_];

    try {
      for (int i = 0; i < number_of_streams_; i++) {
        toutputs[i] = data_sockets_[i].getOutputStream();
      }
    }
    catch (IOException e) {
      throw e;
    }

    //System.out.println("Before POutputStream(toutputs)");

    PTCPOutputStream output = new PTCPOutputStream(toutputs);

    return output;
  }

  /**
   * Returns the remote port to which this socket is connected.
   */
  public int getPort() {
    return comm_socket_.getPort();
  }

  public void setRTT(int rtt) {
    this.RTT = rtt;
  }

  public int getRTT() {
    RTT = end_RTT_ - start_RTT_;

    return (int) (RTT);
  }

  /**
   * Sets the SO_RCVBUF option to the specified value for this Socket. The
   * SO_RCVBUF option is used by the platform's networking code as a hint
   * for the size to set the underlying network I/O buffers.
   *
   * Increasing buffer size can increase the performance of network I/O for
   * high-volume connection, while decreasing it can help reduce the backlog
   * of incoming data. For UDP, this sets the maximum size of a packet that
   * may be sent on this Socket.
   *
   * Because SO_RCVBUF is a hint, applications that want to verify what size
   * the buffers were set to should call getReceiveBufferSize().
   */
  public void setReceiveBufferSize(int size) {
    try {
      size = ( (size < 0) ? 8192 : size);
      if (PTCPServerSocket_Debug) {
        System.out.println(module_name_ + "setReceiveBufferSize " + size);
      }

      if (comm_socket_ != null) {
        comm_socket_.setReceiveBufferSize(size);
      }

      if (data_sockets_ != null) {
        for (int i = 0; i < number_of_streams_; i++) {
          data_sockets_[i].setReceiveBufferSize(size);
        }
      }
    }
    catch (SocketException e) {
      e.printStackTrace();
    }
  }

  /**
   * Gets the value of the SO_RCVBUF option for this Socket, that is the
   * buffer size used by the platform for input on this Socket.
   */
  public int getReceiveBufferSize() {
    try {
      //return commSocket_.getReceiveBufferSize();
      return data_sockets_[0].getReceiveBufferSize();
    }
    catch (SocketException e) {
      e.printStackTrace();
    }

    return 0;
  }

  /**
   * Sets the SO_SNDBUF option to the specified value for this Socket. The
   * SO_SNDBUF option is used by the platform's networking code as a
   * hint for the size to set the underlying network I/O buffers.
   *
   * Increasing buffer size can increase the performance of network I/O for
   * high-volume connection, while decreasing it can help reduce the backlog
   * of incoming data. For UDP, this sets the maximum size of a packet that
   * may be sent on this Socket.
   *
   * Because SO_SNDBUF is a hint, applications that want to verify what size
   * the buffers were set to should call getSendBufferSize().
   * Parameters:
   */
  public void setSendBufferSize(int size) throws SocketException {
    try {
      size = ( (size < 0) ? 8192 : size);
      if (PTCPServerSocket_Debug) {
        System.out.println(module_name_ + "setSendBufferSize " + size);
      }

      if (comm_socket_ != null) {
        comm_socket_.setSendBufferSize(size);
      }

      if (data_sockets_ != null) {
        for (int i = 0; i < number_of_streams_; i++) {
          data_sockets_[i].setSendBufferSize(size);
        }
      }
    }
    catch (SocketException e) {
      throw e;
    }
  }

  /**
   * Get value of the SO_SNDBUF option for this Socket, that is the buffer
   * size used by the platform for output on this Socket.
   */
  public int getSendBufferSize() throws SocketException {
    try {
      //return commSocket_.getSendBufferSize();
      return data_sockets_[0].getReceiveBufferSize();
    }
    catch (SocketException e) {
      throw e;
    }
  }

  /**
   * Enable/disable SO_LINGER with the specified linger time in seconds. The
   * maximum timeout value is platform specific. The setting only affects
   * socket close.
   */
  public void setSoLinger(boolean on, int linger) throws SocketException {
    try {
      if (comm_socket_ != null) {
        comm_socket_.setSoLinger(on, linger);
      }

      if (data_sockets_ != null) {
        for (int i = 0; i < number_of_streams_; i++) {
          data_sockets_[i].setSoLinger(on, linger);
        }
      }
    }
    catch (SocketException e) {
      throw e;
    }
  }

  /**
   * Returns setting for SO_LINGER. -1 returns implies that the option is
   * disabled. The setting only affects socket close.
   */
  public int getSoLinger() throws SocketException {
    try {
      return comm_socket_.getSoLinger();
    }
    catch (SocketException e) {
      throw e;
    }
  }

  public void setIncreasePerformanceOption() {
    increase_performance_ = true;
  }

  /**
   * Enable/disable SO_TIMEOUT with the specified timeout, in milliseconds.
   * With this option set to a non-zero timeout, a read() call on the
   * InputStream associated with this Socket will block for only this amount
   * of time. If the timeout expires, a java.io.InterruptedIOException is
   * raised, though the Socket is still valid. The option must be enabled
   * prior to entering the blocking operation to have effect. The timeout must
   * be > 0. A timeout of zero is interpreted as an infinite timeout.
   */
  public void setSoTimeout(int timeout) throws SocketException {
    try {
      if (comm_socket_ != null) {
        comm_socket_.setSoTimeout(timeout);
      }

      if (data_sockets_ != null) {
        for (int i = 0; i < number_of_streams_; i++) {
          data_sockets_[i].setSoTimeout(timeout);
        }
      }
    }
    catch (SocketException e) {
      throw e;
    }
  }

  /**
   * Returns setting for SO_TIMEOUT. 0 returns implies that the option is
   * disabled (i.e., timeout of infinity).
   */
  public int getSoTimeout() throws SocketException {
    try {
      return comm_socket_.getSoTimeout();
    }
    catch (SocketException e) {
      throw e;
    }
  }

  /**
   * Enable/disable TCP_NODELAY (disable/enable Nagle's algorithm).
   */
  public void setTcpNoDelay(boolean on) throws SocketException {
    try {
      if (comm_socket_ != null) {
        comm_socket_.setTcpNoDelay(on);
      }

      if (data_sockets_ != null) {
        for (int i = 0; i < number_of_streams_; i++) {
          data_sockets_[i].setTcpNoDelay(on);
        }
      }
    }
    catch (SocketException e) {
      throw e;
    }
  }

  /**
   * Tests if TCP_NODELAY is enabled.
   */
  public boolean getTcpNoDelay() throws SocketException {
    try {
      return comm_socket_.getTcpNoDelay();
    }
    catch (SocketException e) {
      e.printStackTrace();
      throw e;
    }
  }

  public void setTxEndTime(long txEndTime) {
    this.tx_end_time_ = txEndTime;
  }

  public long getTxEndTime() {
    return tx_end_time_;
  }

  public void setTxStartTime(long txStartTime) {
    this.tx_start_time_ = txStartTime;
  }

  public long getTxStartTime() {
    return tx_start_time_;
  }

  public void addSocket(Socket[] sockets) {
    for (int i = 0; i < number_of_streams_; i++) {
      if (data_sockets_[i] == null) {
        if (PTCPServerSocket_Debug) {
          System.out.println(module_name_ + i + ". socket is registered!");
        }
        data_sockets_[i] = sockets[i];
      }
      else {
        if (PTCPServerSocket_Debug) {
          System.out.println(module_name_ + "There is a zombie socket");
        }

        break;
      }
    }
  }

  public void addSocket(Socket[] sockets, Socket commSock) {
    comm_socket_ = commSock;
    for (int i = 0; i < number_of_streams_; i++) {
      if (data_sockets_[i] == null) {
        if (PTCPServerSocket_Debug) {
          System.out.println(module_name_ + i + ". socket is registered!");
        }
        data_sockets_[i] = sockets[i];
      }
      else {
        if (PTCPServerSocket_Debug) {
          System.out.println(module_name_ + "There is a zombie socket");
        }

        break;
      }
    }
  }

  /**
   * Places the input stream for this socket at "end of stream".
   * Any data sent to the input stream side of the socket is
   * acknowledged and then silently discarded. If you read from a
   * socket input stream after invoking
   * shutdownInput() on the socket, the stream will return EOF.
   */
  public void shutdownInput() throws IOException {
    try {
      if (comm_socket_ != null) {
        comm_socket_.shutdownInput();
      }

      if (data_sockets_ != null) {
        for (int i = 0; i < number_of_streams_; i++) {
          data_sockets_[i].shutdownInput();
        }
      }
    }
    catch (IOException e) {
      throw e;
    }
  }

  /**
   * Disables the output stream for this socket. For a TCP socket, any
   * previously written data will be sent followed by TCP's normal
   * connection termination sequence. If you write to a socket output
   * stream after invoking shutdownOutput() on the socket, the stream
   * will throw an     IOException.
   */
  public void shutdownOutput() throws IOException {
    try {
      if (comm_socket_ != null) {
        comm_socket_.shutdownOutput();
      }

      if (data_sockets_ != null) {
        for (int i = 0; i < number_of_streams_; i++) {
          data_sockets_[i].shutdownOutput();
        }
      }
    }
    catch (IOException e) {
      throw e;
    }
  }

  /**
   * Converts this socket to a String.
   */
  public String toString() {
    return comm_socket_.toString();
  }

  /**
   * Send initial message to server
   */
  private int getTCPWindowSize() {
    if (getRTT() == 0) {
      TCPWINDOWSIZE = (10 * BANDWIDTH) / 8 / 1000;
    }
    else {
      TCPWINDOWSIZE = (getRTT() * BANDWIDTH) / 8 / 1000;
    }

    return TCPWINDOWSIZE;
  }

  private String createId() {
    String id = null;

    try {
      InetAddress remoteHost = InetAddress.getLocalHost();

      id = remoteHost.getHostAddress() + ":" +
           String.valueOf(System.currentTimeMillis());
    }
    catch (UnknownHostException ex) {
      ex.printStackTrace();
    }

    return id;
  }

  /**
   * Initiate contact to the server side.
   */
  private void init() {
    try {
      sendStartRTT();

      long startRTTEcho = receiveStartRTTEcho();

      if (start_RTT_ == startRTTEcho) {
        if (PTCPServerSocket_Debug) {
          System.out.println(module_name_ + "RTT is " + (end_RTT_ - start_RTT_) +
                             " milliseconds");
        }
        //System.out.println("Optimal TCP Window Size is " + getTCPWindowSize());
        sendOptTCPWindowSize();
        getTCPWindowSize();
      }
      else {
        if (PTCPServerSocket_Debug) {
          System.out.println(module_name_ + "Something is wrong with RTT");
        }
      }

      data_sockets_ = new Socket[number_of_streams_];

      for (int i = 0; i < number_of_streams_; i++) {
        data_sockets_[i] = new Socket();
        //System.out.println("TCP Window Size " + TCPWINDOWSIZE);
        //dataSockets_[i].setReceiveBufferSize(625 * 1024);
        //dataSockets_[i].setSendBufferSize(625 * 1024);

        //dataSockets_[i] = new Socket(commSocket_.getInetAddress(),
        //                             commSocket_.getPort());
        data_sockets_[i].connect(comm_socket_.getRemoteSocketAddress(),
                                 comm_socket_.getPort());

        //System.out.println("Client setReceiveBufferSize "+dataSockets_[i].getReceiveBufferSize());
        //System.out.println("Client setSendBufferSize "+dataSockets_[i].getSendBufferSize());
        registerToServer(data_sockets_[i]);

        //System.out.println(i + ". socket has registered to Server!");
      }

      if (PTCPServerSocket_Debug) {
        System.out.println(module_name_ + "Client finished server connections");
      }
    }
    catch (Exception e) {
      e.printStackTrace();
    }
  }

  private long receiveStartRTTEcho() {
    long startRTTEcho = 0;
    DataInputStream istream = null;
    byte[] header = new byte[8];
    ByteArrayInputStream thstream = new ByteArrayInputStream(header);
    DataInputStream headerStream = new DataInputStream(thstream);

    try {
      istream = new DataInputStream(comm_socket_.getInputStream());
      istream.readFully(header);
    }
    catch (IOException e) {
      e.printStackTrace();
    }

    try {
      startRTTEcho = headerStream.readLong();
      end_RTT_ = System.currentTimeMillis();
    }
    catch (IOException e) {
      e.printStackTrace();
    }

    if (PTCPServerSocket_Debug) {
      System.out.println(module_name_ + "Server sends back a startRTT " +
                         startRTTEcho);
    }

    return startRTTEcho;
  }

  private void registerToServer(Socket socket) {
    byte[] header = null;
    int length = 4 + id_.getBytes().length + 4 + 8;
    OutputStream ostream = null;
    ByteArrayOutputStream thstream = new ByteArrayOutputStream(length);
    DataOutputStream headerStream = new DataOutputStream(thstream);

    // write localport and number streams to the header
    try {
      headerStream.writeInt(id_.getBytes().length);
      headerStream.writeBytes(id_);
      headerStream.writeInt(101);
      headerStream.writeLong(System.currentTimeMillis());
    }
    catch (IOException e) {
      e.printStackTrace();
    }

    // send header to the server.
    header = thstream.toByteArray();

    if (header.length != length) {
      System.out.println("Incorrect header length\n");
    }

    try {
      ostream = socket.getOutputStream();
      ostream.write(header);
      ostream.flush();
    }
    catch (IOException e) {
      e.printStackTrace();
    }
  }

  private void sendOptTCPWindowSize() {
    byte[] header = null;
    int length = 8;
    OutputStream ostream = null;
    ByteArrayOutputStream thstream = new ByteArrayOutputStream(length);
    DataOutputStream headerStream = new DataOutputStream(thstream);

    // write localport and number streams to the header
    try {
      headerStream.writeInt(getTCPWindowSize());
      headerStream.writeInt(getRTT());
    }
    catch (IOException e) {
      e.printStackTrace();
    }

    // send header to the server.
    header = thstream.toByteArray();

    if (header.length != length) {
      System.out.println(module_name_ + "Incorrect header length\n");
    }

    try {
      ostream = comm_socket_.getOutputStream();
      ostream.write(header);
      ostream.flush();
    }
    catch (IOException e) {
      e.printStackTrace();
    }
  }

  private void sendStartRTT() {
    byte[] header = null;
    int length = 4 + id_.getBytes().length + 4 + 8 + 4 +
                 8;
    OutputStream ostream = null;
    ByteArrayOutputStream thstream = new ByteArrayOutputStream(length);
    DataOutputStream headerStream = new DataOutputStream(thstream);

    // write localport and number streams to the header
    try {
      start_RTT_ = System.currentTimeMillis();

      //headerStream.writeInt(length);
      headerStream.writeInt(id_.getBytes().length);
      headerStream.writeBytes(id_);
      headerStream.writeInt(100);
      headerStream.writeLong(System.currentTimeMillis());
      headerStream.writeInt(number_of_streams_);
      headerStream.writeLong(start_RTT_);
    }
    catch (IOException e) {
      e.printStackTrace();
    }

    // send header to the server.
    header = thstream.toByteArray();

    if (header.length != length) {
      System.out.println(module_name_ + "Incorrect header length\n");
    }

    try {
      ostream = comm_socket_.getOutputStream();
      ostream.write(header);
      ostream.flush();
    }
    catch (IOException e) {
      e.printStackTrace();
    }
  }

  /**
   * Returns the connection state of the socket.
   *
   * @return true if the socket successfuly connected to a server
   * TODO: Implement this java.net.Socket method
   */
  public boolean isConnected() {
    return comm_socket_.isConnected();
  }

  public boolean isIncrPerf() {
    return increase_performance_;
  }

  public void setIncrPerf(boolean incrPerf) {
    this.increase_performance_ = incrPerf;
  }
  public int getNumberOfStreams() {
    return number_of_streams_;
  }

}
